﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.RestrictedUsage.CSharp.Semantics;

namespace devtm.Documentation.CSharpModel
{

    public class CSharpNameFormatter
    {
        // Fields
        private static Dictionary<string, string> PredefinedTypeNames = new Dictionary<string, string>();

        // Methods
        static CSharpNameFormatter()
        {
            PredefinedTypeNames["Void"] = "void";
            PredefinedTypeNames["Boolean"] = "bool";
            PredefinedTypeNames["Byte"] = "byte";
            PredefinedTypeNames["SByte"] = "sbyte";
            PredefinedTypeNames["Char"] = "char";
            PredefinedTypeNames["Decimal"] = "decimal";
            PredefinedTypeNames["Double"] = "double";
            PredefinedTypeNames["Single"] = "float";
            PredefinedTypeNames["Int32"] = "int";
            PredefinedTypeNames["UInt32"] = "uint";
            PredefinedTypeNames["Int64"] = "long";
            PredefinedTypeNames["UInt64"] = "ulong";
            PredefinedTypeNames["Object"] = "object";
            PredefinedTypeNames["Int16"] = "short";
            PredefinedTypeNames["UInt16"] = "ushort";
            PredefinedTypeNames["String"] = "string";
        }

        public CSharpNameFormatter()
        {
            this.IncludeContainingTypes = true;
            this.IncludeMemberContainingType = true;
            this.IncludeMemberParameterNames = true;
            this.IncludeMemberParameters = true;
            this.IncludeNamespace = true;
            this.IncludeMemberReturnType = true;
            this.IncludeTypeParameters = true;
            this.ReplaceTypesWithKeywords = true;
            this.ReturnTypePlacement = ReturnTypePlacementMode.Postfix;
        }

        private void FormatArrayRank(int rank, StringBuilder builder)
        {
            builder.Append('[');
            builder.Append(',', rank - 1);
            builder.Append(']');
        }

        private void FormatCompleteTypeName(CSharpType type, StringBuilder builder)
        {
            if (type.IsArray)
            {
                this.FormatCompleteTypeName(type.ElementType, builder);
                this.FormatArrayRank(type.Rank, builder);
            }
            else if (type.IsPointer)
            {
                this.FormatCompleteTypeName(type.ElementType, builder);
                builder.Append("*");
            }
            else if (type.IsDynamic)
            {
                builder.Append("dynamic");
            }
            else if (type.IsAnonymousType)
            {
                builder.Append("anonymous");
            }
            else if ((!type.IsPredefinedType || !this.ReplaceTypesWithKeywords) || !this.FormatPredefinedType(type, builder))
            {
                Stack<CSharpType> stack = new Stack<CSharpType>();
                if (this.IncludeContainingTypes || this.IncludeNamespace)
                {
                    while (type.IsNested)
                    {
                        stack.Push(type);
                        type = type.ContainingType;
                    }
                }
                stack.Push(type);
                this.FormatTypeNamespace(type, builder);
                while (stack.Count > 1)
                {
                    this.FormatTypeNameWithGenericParameters(stack.Pop(), builder);
                    builder.Append('.');
                }
                this.FormatTypeNameWithGenericParameters(stack.Pop(), builder);
            }
        }

        private void FormatGenericArgumentList(IList<CSharpType> typeList, StringBuilder builder)
        {
            if (this.IncludeTypeParameters && (typeList.Count > 0))
            {
                builder.Append('<');
                this.FormatCompleteTypeName(typeList[0], builder);
                for (int i = 1; i < typeList.Count; i++)
                {
                    builder.Append(", ");
                    this.FormatCompleteTypeName(typeList[i], builder);
                }
                builder.Append('>');
            }
        }

        private void FormatMemberContainingType(CSharpMember member, StringBuilder builder)
        {
            if (this.IncludeMemberContainingType && (member.ExplicitInterfaceImplementation == null))
            {
                this.FormatCompleteTypeName(member.ContainingType, builder);
                builder.Append(".");
            }
        }

        public string FormatMemberName(CSharpMember member)
        {
            StringBuilder builder = new StringBuilder();
            this.FormatMemberWithParameterList(member, builder);
            return builder.ToString();
        }

        private void FormatMemberParameterList(IList<CSharpParameter> parameters, StringBuilder builder)
        {
            if (this.IncludeMemberParameters)
            {
                builder.Append("(");
                if (parameters.Count > 0)
                {
                    this.FormatParameter(parameters[0], builder);
                    for (int i = 1; i < parameters.Count; i++)
                    {
                        builder.Append(", ");
                        this.FormatParameter(parameters[i], builder);
                    }
                }
                builder.Append(")");
            }
        }

        private void FormatMemberWithParameterList(CSharpMember member, StringBuilder builder)
        {
            this.FormatPrefixReturnType(member, builder);
            this.FormatMemberContainingType(member, builder);
            if (member.ExplicitInterfaceImplementation != null)
            {
                this.FormatCompleteTypeName(member.ExplicitInterfaceImplementation, builder);
                builder.Append(".");
            }
            builder.Append(member.Name.Text);
            this.FormatGenericArgumentList(member.TypeParameters, builder);
            if (member.IsMethod || member.IsConstructor)
            {
                this.FormatMemberParameterList(member.Parameters, builder);
            }
            this.FormatPostfixReturnType(member, builder);
        }

        public string FormatParameter(CSharpParameter parameter)
        {
            StringBuilder builder = new StringBuilder();
            this.FormatParameter(parameter, builder);
            return builder.ToString();
        }

        private void FormatParameter(CSharpParameter parameter, StringBuilder builder)
        {
            if (this.IncludeMemberParameterNames)
            {
                if (this.ReturnTypePlacement == ReturnTypePlacementMode.Prefix)
                {
                    this.FormatParameterType(parameter, builder);
                    builder.Append(" ");
                    builder.Append(parameter.Name.Text);
                }
                else
                {
                    builder.Append(parameter.Name.Text);
                    builder.Append(" : ");
                    this.FormatParameterType(parameter, builder);
                }
            }
            else
            {
                this.FormatParameterType(parameter, builder);
            }
        }

        private void FormatParameterType(CSharpParameter parameter, StringBuilder builder)
        {
            this.FormatCompleteTypeName(parameter.Type, builder);
            if (parameter.IsRef || parameter.IsOut)
            {
                builder.Append("&");
            }
        }

        private void FormatPostfixReturnType(CSharpMember member, StringBuilder builder)
        {
            if ((this.IncludeMemberReturnType && (this.ReturnTypePlacement == ReturnTypePlacementMode.Postfix)) && ((member.ReturnType != null) && !member.IsEnumMember))
            {
                builder.Append(" : ");
                this.FormatCompleteTypeName(member.ReturnType, builder);
            }
        }

        private bool FormatPredefinedType(CSharpType type, StringBuilder builder)
        {
            string str;
            if (PredefinedTypeNames.TryGetValue(type.Name.Text, out str))
            {
                builder.Append(str);
                return true;
            }
            return false;
        }

        private void FormatPrefixReturnType(CSharpMember member, StringBuilder builder)
        {
            if ((this.IncludeMemberReturnType && (this.ReturnTypePlacement == ReturnTypePlacementMode.Prefix)) && ((member.ReturnType != null) && !member.IsEnumMember))
            {
                this.FormatCompleteTypeName(member.ReturnType, builder);
                builder.Append(" ");
            }
        }

        public string FormatTypeName(CSharpType type)
        {
            StringBuilder builder = new StringBuilder();
            this.FormatCompleteTypeName(type, builder);
            return builder.ToString();
        }

        private void FormatTypeNamespace(CSharpType type, StringBuilder builder)
        {
            if (this.IncludeNamespace)
            {
                if (type.IsArray || type.IsPointer)
                {
                    this.FormatTypeName(type.ElementType);
                }
                string fullNamespace = null;
                if (type.ContainingNamespace != null)
                {
                    fullNamespace = type.ContainingNamespace.GetFullNamespace();
                }
                if (!string.IsNullOrEmpty(fullNamespace))
                {
                    builder.Append(fullNamespace);
                    builder.Append(".");
                }
            }
        }

        private void FormatTypeNameWithGenericParameters(CSharpType type, StringBuilder builder)
        {
            builder.Append(type.Name.Text);
            if (type.TypeArguments.Count == type.TypeParameters.Count)
            {
                this.FormatGenericArgumentList(type.TypeArguments, builder);
            }
            else
            {
                this.FormatGenericArgumentList(type.TypeParameters, builder);
            }
        }

        // Properties
        public bool IncludeContainingTypes { get; set; }

        public bool IncludeMemberContainingType { get; set; }

        public bool IncludeMemberParameterNames { get; set; }

        public bool IncludeMemberParameters { get; set; }

        public bool IncludeMemberReturnType { get; set; }

        public bool IncludeNamespace { get; set; }

        public bool IncludeTypeParameters { get; set; }

        public bool ReplaceTypesWithKeywords { get; set; }

        public ReturnTypePlacementMode ReturnTypePlacement { get; set; }

        // Nested Types
        public enum ReturnTypePlacementMode
        {
            Prefix,
            Postfix
        }
    }


}
